package com.coderxq.blog.exception;

import cn.hutool.core.util.StrUtil;
import com.coderxq.blog.utils.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;


@Slf4j
@RestControllerAdvice   //定义该类为捕获全局异常类
public class GlobalExceptionAdvice {

    //java内置异常报错时捕获
    @ExceptionHandler(Exception.class)
    public ResponseResult<Object> error(Exception e){
        // 异常日志 抛出需要被捕获
        log.error(">>> 服务器未知异常，具体信息：" + e.getMessage(), e);
        return new ResponseResult<>(500, "error", e.getMessage());
    }

    /**
     * 处理自定义异常（前端直接右上角弹窗显示异常信息）
     */
    @ExceptionHandler(WsException.class)  //当项目中抛出WsException自定义类型的异常时，会被该方法捕获到进入到该方法中
    public ResponseResult<Object> handleException(WsException e) {
        log.error(e.getMessage(), e);
        return new ResponseResult<>(401, e.getMessage()); //像浏览器返回自定义响应结果
    }



    /**
     * JSR303n 请求参数校验异常，嵌套检验问题
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    // @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseResult<Object> constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
        log.warn("ConstraintViolationException:", ex);
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
        return new ResponseResult<>(500, message);
    }

    /**
     * JSR303n 当控制器方法参数校验失败时，Spring会抛出这个异常。
     * @param ex 被处理的异常对象，包含了有关校验失败的信息 @NotNull(message = "不能为空")
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    // @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseResult<Object> methodArgumentNotValidException(MethodArgumentNotValidException ex) {
        log.warn("MethodArgumentNotValidException:", ex);
        String defaultMessage = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage();
        return new ResponseResult<>(500, defaultMessage);
    }

    /**
     * 空参异常处理
     * @param ex
     * @param request
     * @return
     */
    @ExceptionHandler(BindException.class)
    // @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseResult<Void> bindException(BindException ex, HttpServletRequest request) {
        log.warn("BindException:", ex);
        try {
            // 拿到@NotNull,@NotBlank和 @NotEmpty等注解上的message值
            String msg = Objects.requireNonNull(ex.getBindingResult().getFieldError()).getDefaultMessage();
            if (StrUtil.isNotEmpty(msg)) {
                // 自定义状态返回
                return new ResponseResult<>(500, msg);
            }
        } catch (Exception ignored) {
        }
        // 参数类型不匹配检验
        StringBuilder msg = new StringBuilder();
        List<FieldError> fieldErrors = ex.getFieldErrors();
        fieldErrors.forEach((oe) ->
                msg.append("参数:[").append(oe.getObjectName())
                        .append(".").append(oe.getField())
                        .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.")
        );
        return new ResponseResult<>(500, msg.toString());
    }

}